package com.tistroy.admin.common.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Reflection을 처리하는 유틸 클래스
 * 
 * @author 정화수
 * 
 */
public abstract class ReflectionUtil {

	private ReflectionUtil() {
	}

	/**
	 * 객체에 선언된 field 목록을 가져온다.
	 * 
	 * @param object
	 *            조사할 객체
	 * @return 객체에 선언된 field 목록
	 */
	public static Field[] getFields(Object object) {

		return object.getClass().getDeclaredFields();

	}

	/**
	 * 객체에 선언된 field 이름 목록을 가져온다.
	 * 
	 * @param object
	 *            조사할 객체
	 * @return 객체에 선언된 field 목록
	 */
	public static List<String> getFieldNames(Object object) {

    	List<String> result = new ArrayList<String>();
    	
    	if( object != null ) {

    		Field[] declaredFields = object.getClass().getDeclaredFields();
    		
    		for( Field field : declaredFields ) {
    			
    			// Skip Synthetic Field
    			if( (field.getModifiers() & 0x00001000 ) != 0 ) continue;
    			
    			result.add( field.getName() );
    			
    		}
    		
    	}

        return result;

	}

	public static List<String> getFieldNames(Map<?, ?> object) {

		List<String> result = new ArrayList<String>();

		Set<?> keySet = object.keySet();

		for (Object key : keySet) {

			result.add(key.toString());

		}

		return result;

	}

	/**
	 * 객체에 선언된 field에 담긴 값을 가져온다.
	 * 
	 * @param object
	 *            조사할 객체
	 * @param field
	 *            객체에 선언된 field
	 * @return field에 담겨있는 값
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 */
	public static Object getFieldValue(Object object, Field field) throws IllegalArgumentException,
			IllegalAccessException {

		field.setAccessible(true);

		return field.get(object);

	}

	/**
	 * 
	 * 객체에 선언된 field에 담긴 값을 가져온다.
	 * 
	 * @param object
	 *            조사할 객체
	 * @param fieldName
	 *            객체에 선언된 field 명
	 * @return
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	public static Object getFieldValue(Object object, String fieldName) throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException  {

		Field field = object.getClass().getDeclaredField( fieldName );
		field.setAccessible(true);

		return field.get(object);

	}

	/**
	 * 객체에 선언된 field에 값을 담는다.
	 * 
	 * @param object
	 *            조사할 객체
	 * @param field
	 *            객체에 선언된 field
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 */
	public static void setFieldValue(Object object, Field field, Object value) throws IllegalArgumentException,
			IllegalAccessException {

		field.setAccessible(true);

		field.set(object, value);

	}

	/**
	 * 객체에 선언된 field에 값을 담는다.
	 * 
	 * @param object
	 *            조사할 객체
	 * @param fieldName
	 *            객체에 선언된 field 명
	 * @param value
	 *            field에 담을 값
	 * @throws NoSuchFieldException
	 * @throws SecurityException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 */
	public static void setFieldValue(Object object, String fieldName, Object value) throws SecurityException,
			NoSuchFieldException, IllegalArgumentException, IllegalAccessException {

		Field field = object.getClass().getField(fieldName);

		field.setAccessible(true);

		field.set(object, value);

	}

	/**
	 * 객체의 field 이름과 값을 쌍으로 갖는 데이터를 구한다.
	 * 
	 * @param object
	 *            조사할 객체
	 * @return field 이름과 값을 쌍으로 갖는 데이터
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 */
	public static Map<String, Object> getFieldMap(Object object) throws IllegalArgumentException,
			IllegalAccessException {

		Field[] declaredFields = object.getClass().getDeclaredFields();

		Map<String, Object> result = new HashMap<String, Object>();

		for (Field field : declaredFields) {

			field.setAccessible(true);

			result.put(field.getName(), field.get(object));

		}

		return result;

	}

	/**
	 * 객체를 복사한다. (객체에 담긴 데이터까지 모두 포함)
	 * 
	 * @param objectToClone
	 *            복사할 객체
	 * @return 복사한 객체
	 * @throws IOException
	 *             , ClassNotFoundException
	 */
	@SuppressWarnings("unchecked")
	public static <T> T deepClone(T objectToClone) throws IOException, ClassNotFoundException {

		if (objectToClone == null)
			return null;

		ObjectOutputStream oos = null;
		ObjectInputStream ois = null;

		try {

			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(bos);

			oos.writeObject(objectToClone);
			oos.flush();

			ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
			ois = new ObjectInputStream(bis);

			return (T) ois.readObject();

		} catch (IOException e) {
			throw e;

		} finally {

			if (oos != null) {
				try {
					oos.close();
				} catch (Exception e) {
				}
			}
			if (ois != null) {
				try {
					ois.close();
				} catch (Exception e) {
				}
			}

		}

	}

}
